#include"mythread.hpp"
#include"LockGuard.hpp"
#include"Task.hpp"
#include<vector>
#include<queue>

//模拟实现一个线程池

const int gnum = 5;

template<class T>
class ThreadPool; //前置声明

template<class T>
class ThreadData //当结构体使用
{
public:
    string _name;
    ThreadPool<T>* _threadpool;

    ThreadData(const string& name, ThreadPool<T>* tp)
    :_name(name), _threadpool(tp)
    {}

};

template<class T>
class ThreadPool
{ 
public:
    ThreadPool(const int& num = gnum)
    :_num(gnum)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
        for(int i = 0; i < _num; i++)
        {
            // 所有线程被创建出来都要有自己的任务
            _threads.push_back(new Thread());  
        }
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
        for(auto& t : _threads)
        {
            t->join();
            delete t;
        }
    }

    void run()
    {
        for(auto& t : _threads)
        {
            ThreadData<T>* td = new ThreadData<T>(t->getname(), this); //不传this指针，无法访问类内成员

            t->start(handlerTask, td); 
            std::cout << t->getname() << ": running\n";
        }
    }

    //向线程池发送一个任务
    void Push(const T& in)
    {
        LockGuard lock(&_mutex); //使用RAII风格来进行加锁解锁
        // pthread_mutex_lock(&_mutex);
        _task_queue.push(in);
        //插入数据之后就唤醒线程去执行
        pthread_cond_signal(&_cond);
        // pthread_mutex_unlock(&_mutex);
    }

private:
    //这里这个函数必须是static的，因为不这样会传this指针进来
    static void* handlerTask(void* args) // args就是传进来的this指针
    {
        ThreadData<T>* td = static_cast<ThreadData<T>*>(args); //利用td里存储的this指针来访问类内的成员

        //不断的取任务，每个线程都要取，取完就执行
        while(1)
        {
            T t;
            { // 限制一下lock的生命周期。在解锁之后要处理任务
                LockGuard lock(td->_threadpool->getmutex());
                while(td->_threadpool->is_QueueEmpty()) //用while不用if，原因之前讲过了，不重复
                {
                    td->_threadpool->threadWait();
                }
                //走到这里就可以访问临界资源了
                t = td->_threadpool->pop(); // pop的本质——将资源从临界资源中，拿到线程自己的独立栈结构中
                // 任务处理不能放在这里，要在解锁之后处理
            }
            cout << td->_name << "处理任务： " << t.toTaskString() << " | 结果为：" << t() << endl;
            //处理任务应该放在解锁之后，不然一旦任务执行的时间过长，那么其他所有的线程都无法访问任务队列，
            //即便存在线程切换也不行，因为加锁的本质(忘记就要复习，这里不重复)
            // 这样才能实现多线程的并发处理任务【效率才高】
        }
        delete td;
        return nullptr;
    }

    void lockQueue(){
        pthread_mutex_lock(&_mutex);
    }

    void unlockQueue(){
        pthread_mutex_unlock(&_mutex);
    }

    bool is_QueueEmpty(){
        return _task_queue.empty();
    }

    void threadWait(){
        pthread_cond_wait(&_cond, &_mutex);
    }

    T pop(){
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }

    pthread_mutex_t* getmutex(){
        return &_mutex;
    }

private:
    int _num; //存放的线程个数
    vector<Thread*> _threads; //线程池中存放的线程
    queue<T> _task_queue; // 每个线程的任务就是将任务队列的任务拿出来执行
    pthread_mutex_t _mutex; // 保护
    pthread_cond_t _cond;

};